package com.symaster.common.geom;

import com.symaster.common.geom.entity.Node;
import com.symaster.common.geom.entity.Point;
import com.symaster.common.geom.entity.Polygon;
import com.symaster.common.geom.entity.Vector;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author yinmiao
 * @version 2022-06-03 11:28
 */
public class LayoutPanel implements ViewPolygonPanel {

    private static final double precision = 0.1;
    private final double width;
    private final double height;
    private final double marge;
    private final Map<Integer, Node> nodeIdMap;
    private double useWidth;
    private double useHeight;

    public LayoutPanel(double width, double height, double marge) {
        this.width = width;
        this.height = height;
        this.marge = marge;
        this.nodeIdMap = new HashMap<>();
        this.useWidth = 0;
        this.useHeight = 0;
    }

    public void setStaticNodes(Collection<Node> staticNodes) {
        if (staticNodes == null || staticNodes.size() - 1 < 0) {
            return;
        }
        for (Node staticNode : staticNodes) {
            putToMap(staticNode, "(staticNode.getId() == null)", "(nodeIdMap.get(staticNode.getId()) != null)");
        }
    }

    public void addStaticNodes(Node... staticNodes) {
        if (staticNodes == null || staticNodes.length - 1 < 0) {
            return;
        }
        for (Node staticNode : staticNodes) {
            putToMap(staticNode, "(staticNode.getId() == null)", "(nodeIdMap.get(staticNode.getId()) != null)");
            updateUseWH(staticNode.getId());
        }
    }

    public void addNode(Node node) {
        putToMap(node, "(node.getId() == null)", "(nodeIdMap.get(node.getId()) != null)");
        initPoint(node.getId());
        autoLayout(node.getId());
        updateUseWH(node.getId());
    }

    public void updateUseWH() {
        this.useWidth = 0;
        this.useHeight = 0;
        for (Integer id : nodeIdMap.keySet()) {
            updateUseWH(id);
        }
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }

    public Map<Integer, Node> getNodeIdMap() {
        return nodeIdMap;
    }

    public Double getUseWidth() {
        return useWidth;
    }

    public Double getUseHeight() {
        return useHeight;
    }

    private void updateUseWH(Integer id) {
        Node node = nodeIdMap.get(id);
        double v0 = node.getX() + node.getWidth();
        double v1 = node.getY() + node.getHeight();
        if (v0 >= this.useWidth) {
            this.useWidth = v0;
        }
        if (v1 >= this.useHeight) {
            this.useHeight = v1;
        }
    }

    private void autoLayout(Integer id) {
        Node node = getNode(id);
        if (nodeIdMap.size() <= 1) {
            node.move(0, 0);
            return;
        }

        double offset = (marge < 1) ? 1 : marge * 0.5;
        Point minPoint = new Point(node.getX(), node.getY());
        while (node.getX() >= 0) {
            Point tempPoint = new Point(node.getX(), node.getY());
            while (node.getY() >= 0) {
                if (translateY(node, -offset)) {
                    minPoint = minPoint(new Point(node.getX(), node.getY()), minPoint);
                } else {
                    break;
                }
            }
            tempPoint.setX(tempPoint.x - offset);
            node.move(tempPoint);
        }
        node.move(minPoint);
    }

    private Point minPoint(Point p1, Point p2) {
        if (p1.getY() < p2.getY()) {
            return p1;
        }

        if (Math.abs(p1.getY() - p2.getY()) <= 1.0 && p1.getX() < p2.getX()) {
            return p1;
        }
        return p2;
    }

    private boolean translateX(Node node, double xn) {
        node.translate(new Vector(xn, 0));
        return notMoveOver(node.getId());
    }

    private boolean translateY(Node node, double yn) {
        node.translate(new Vector(0, yn));
        return notMoveOver(node.getId());
    }

    public boolean notMoveOver(Integer id) {

        Node node = getNode(id);

        /* 判断是否移出画布 */
        if (graphicsOutOfBounds(node)) {
            return false;
        }

        /* 测试是否与其他图形重叠 */
        double marge = this.marge;
        List<Node> nodes = fontNodesByRectangleArea((node.getX() - marge), (node.getY() - marge), (node.getWidth() + (marge * 2)), (node.getHeight() + (marge * 2)));

        for (Node nodeItem : nodes) {
            if (nodeItem.getId().equals(id)) {
                continue;
            }
            if (node.getSwellPolygon().collide(nodeItem.getPolygon())) {
                return false;
            }
        }

        return true;
    }

    public List<Node> fontNodesByRectangleArea(double x, double y, double width, double height) {
        Collection<Node> values = nodeIdMap.values();
        ArrayList<Node> nodes = new ArrayList<>(values.size());
        for (Node node : values) {
            if (node.getX() > x + width) {
                continue;
            }
            if (node.getY() > y + height) {
                continue;
            }
            if (node.getX() + node.getWidth() < x) {
                continue;
            }
            if (node.getY() + node.getHeight() < y) {
                continue;
            }
            nodes.add(node);
        }
        return nodes;
    }

    private boolean graphicsOutOfBounds(Node node) {
        if (node.getX() <= 0 || node.getY() <= 0) {
            return true;
        }
        return node.getX() + node.getWidth() > width;
    }

    private void initPoint(Integer id) {
        if (nodeIdMap.size() <= 0) {
            getNode(id).move(0, 0);
            return;
        }
        Node node = getNode(id);
        node.move(width - node.getWidth() - precision, useHeight + marge + precision);
    }

    private Node getNode(Integer id) {
        return nodeIdMap.get(id);
    }

    private void putToMap(Node node, String s, String s1) {
        if (node.getId() == null) {
            throw new IllegalArgumentException(s);
        }
        if (nodeIdMap.get(node.getId()) != null) {
            throw new IllegalArgumentException(s1);
        }
        nodeIdMap.put(node.getId(), node);
    }

    @Override
    public Collection<Polygon> getPolygons() {
        return nodeIdMap.values().stream().map(Node::getPolygon).distinct().collect(Collectors.toList());
    }

    @Override
    public int size() {
        return nodeIdMap.size();
    }
}
